package com.jacobson.core.widget.security.pam

import com.jacobson.core.widget.logger.LoggerFactory
import com.jacobson.core.widget.security.authc.AuthenticationInfo
import com.jacobson.core.widget.security.authc.AuthenticationToken
import com.jacobson.core.widget.security.authc.Authenticator
import com.jacobson.core.widget.security.authc.LogoutAware
import com.jacobson.core.widget.security.exception.AuthenticationException
import com.jacobson.core.widget.security.exception.UnknownAccountException
import com.jacobson.core.widget.security.exception.UnsupportedTokenException
import com.jacobson.core.widget.security.realm.Realm

/**
 * ================================================
 *
 *  @author Wucz on 2018/8/6 15:42
 * ================================================
 */
class RealmAuthenticator : Authenticator, LogoutAware {
    private val logger = LoggerFactory.getLogger()
    private val realms: MutableList<Realm> = arrayListOf()

    @Throws(AuthenticationException::class)
    override fun authenticate(token: AuthenticationToken): AuthenticationInfo {
        try {
            return this.doAuthenticate(token)?.let {
                logger.d("Authentication successful for token [{}].  Returned account [{}]", token, it)
                this.notifySuccess(token, it)
                it
            } ?: let {
                val t = "No account information found for authentication token [$token] by this Authenticator instance.  Please check that it is configured correctly."
                throw AuthenticationException(t)
            }
        } catch (e: Throwable) {
            try {
                this.notifyFailure(token, e)
            } catch (var7: Throwable) {
                val msg = "Unable to send notification for failed authentication attempt - listener error?.  Please check your AuthenticationListener implementation(s).  Logging sending exception and propagating original AuthenticationException instead..."
                logger.w(msg)
            }

            if (e is AuthenticationException) {
                throw e
            }
            val t2 = "Authentication failed for token submission [$token].  Possible unexpected error? (Typical or expected login exceptions should extend from AuthenticationException)."
            throw AuthenticationException(t2, e)
        }
    }

    /**
     * 通知失败
     */
    private fun notifyFailure(token: AuthenticationToken, throwable: Throwable) {

    }

    /**
     * 通知成功
     */
    private fun notifySuccess(token: AuthenticationToken, info: AuthenticationInfo) {

    }

    private fun doAuthenticate(token: AuthenticationToken): AuthenticationInfo? {
        this.assertRealmsConfigured()
        if (realms.size == 1) {
            return doSingleRealmAuthentication(realms[0], token)
        }
        return this.doMultiRealmAuthentication(realms, token)
    }

    @Throws(UnsupportedTokenException::class, UnknownAccountException::class)
    private fun doSingleRealmAuthentication(realm: Realm, token: AuthenticationToken): AuthenticationInfo {
        if (!realm.supports(token)) {
            val info1 = "Realm [$realm] does not support authentication token [$token].  Please ensure that the appropriate Realm implementation is configured correctly or that the realm accepts AuthenticationTokens of this type."
            throw UnsupportedTokenException(info1)
        } else {
            val info = realm.getAuthenticationInfo(token)
            return info ?: let {
                val msg = "Realm [$realm] was unable to find account data for the submitted AuthenticationToken [$token]."
                throw UnknownAccountException(msg)
            }
        }
    }

    private fun doMultiRealmAuthentication(realms: List<Realm>, token: AuthenticationToken): AuthenticationInfo? {
        logger.d("Iterating through {} realms for PAM authentication", Integer.valueOf(realms.size))
        realms.forEach {
            if (it.supports(token)) {
                try {
                    val info = it.getAuthenticationInfo(token)
                    if (info != null) {
                        return info
                    }
                } catch (e: Throwable) {
                    val msg = "Realm [$it] threw an exception during a multi-realm authentication attempt:"
                    logger.i(msg, e)
                }
            } else {
                logger.d("Realm [{}] does not support token {}.  Skipping realm.", it, token)
            }
        }
        return null
    }

    private fun assertRealmsConfigured() {
        if (realms.size == 0) {
            val msg = "Configuration error:  No realms have been configured!  One or more realms must be present to execute an authentication attempt."
            throw IllegalStateException(msg)
        }
    }

    override fun onLogout(principal: Any) {
        realms.forEach {
            (it as? LogoutAware)?.onLogout(principal)
        }
    }

    fun setRealms(realms: List<Realm>) {
        this.realms.addAll(realms)
    }
}